/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections;
using System.IO;

namespace Apache.NMS.ActiveMQ.OpenWire
{
	/// <summary>
	/// A BinaryWriter that switches the endian orientation of the write opperations so that they
	/// are compatible with marshalling used by OpenWire.
	/// </summary>
	[CLSCompliant(false)]
	public class OpenWireBinaryWriter : BinaryWriter
	{
		public const int MAXSTRINGLEN = short.MaxValue;

		public OpenWireBinaryWriter(Stream output)
			: base(output)
		{
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">A  long</param>
		public override void Write(long value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">An ushort</param>
		public override void Write(ushort value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">An int</param>
		public override void Write(int value)
		{
			int x = EndianSupport.SwitchEndian(value);
			base.Write(x);
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="chars">A  char[]</param>
		/// <param name="index">An int</param>
		/// <param name="count">An int</param>
		public override void Write(char[] chars, int index, int count)
		{
			char[] t = new char[count];
			for(int i = 0; i < count; i++)
			{
				t[index + i] = EndianSupport.SwitchEndian(t[index + i]);
			}
			base.Write(t);
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="chars">A  char[]</param>
		public override void Write(char[] chars)
		{
			Write(chars, 0, chars.Length);
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">An uint</param>
		public override void Write(uint value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}


		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="ch">A  char</param>
		public override void Write(char ch)
		{
			base.Write((byte) ((ch >> 8) & 0xFF));
			base.Write((byte) (ch & 0xFF));
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">An ulong</param>
		public override void Write(ulong value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">A  short</param>
		public override void Write(short value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}

		/// <summary>
		/// Method Write, writes a string to the output using the WriteString16
        /// method.
		/// </summary>
		/// <param name="text">A  string</param>
        public override void Write(String text)
		{
            WriteString16(text);
		}

		/// <summary>
        /// Method WriteString16, writes a string to the output using the Java 
        /// standard modified UTF-8 encoding with an unsigned short value written first to 
        /// indicate the length of the encoded data, the short is read as an unsigned 
        /// value so the max amount of data this method can write is 65535 encoded bytes.
        /// 
        /// Unlike the WriteString32 method this method does not encode the length
        /// value to -1 if the string is null, this is to match the behaviour of 
        /// the Java DataOuputStream class's writeUTF method.	
        /// 
        /// Because modified UTF-8 encding can result in a number of bytes greater that 
        /// the size of the String this method must first check that the encoding proces 
        /// will not result in a value that cannot be written becuase it is greater than
        /// the max value of an unsigned short.
        /// </summary>
		/// <param name="text">A  string</param>
		public void WriteString16(String text)
		{
            if (text != null)
            {
                if (text.Length > ushort.MaxValue)
                {
                    throw new IOException(
                        String.Format(
                            "Cannot marshall string longer than: {0} characters, supplied string was: " +
                            "{1} characters", ushort.MaxValue, text.Length));
                }

                char[] charr = text.ToCharArray();
                uint utfLength = CountUtf8Bytes(charr);

                if (utfLength > ushort.MaxValue)
                {
                    throw new IOException(
                        String.Format(
                            "Cannot marshall an encoded string longer than: {0} bytes, supplied" +
                            "string requires: {1} characters to encode", ushort.MaxValue, utfLength));
                }

                byte[] bytearr = new byte[utfLength];
                encodeUTF8toBuffer(charr, bytearr);

                Write( (ushort)utfLength);
                Write(bytearr);
            }
		}

        /// <summary>
        /// Method WriteString32, writes a string to the output using the Openwire 
        /// standard modified UTF-8 encoding which an int value written first to 
        /// indicate the length of the encoded data, the int is read as an signed 
        /// value so the max amount of data this method can write is 2^31 encoded bytes.
        /// 
        /// In the case of a null value being passed this method writes a -1 to the 
        /// stream to indicate that the string is null.
        /// 
        /// Because modified UTF-8 encding can result in a number of bytes greater that 
        /// the size of the String this method must first check that the encoding proces 
        /// will not result in a value that cannot be written becuase it is greater than
        /// the max value of an int.
        /// </summary>
        /// <param name="text">A  string</param>
        public void WriteString32(String text)
        {
            if( text != null ) {

				char[] charr = text.ToCharArray();
                uint utfLength = CountUtf8Bytes(charr);

                if( utfLength > int.MaxValue ) 
                {
					throw new IOException(
                        String.Format(
                            "Cannot marshall an encoded string longer than: {0} bytes, supplied" + 
                            "string requires: {1} characters to encode", int.MaxValue, utfLength ) );
                }

                byte[] bytearr = new byte[utfLength];
                encodeUTF8toBuffer(charr, bytearr);

                Write(utfLength);
                Write(bytearr);
            } 
            else
            {
                Write((int)-1);
            }
        }

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">A  double</param>
		public override void Write(float value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}

		/// <summary>
		/// Method Write
		/// </summary>
		/// <param name="value">A  double</param>
		public override void Write(double value)
		{
			base.Write(EndianSupport.SwitchEndian(value));
		}

        private uint CountUtf8Bytes(char[] chars)
        {
            uint utfLength = 0;
            int c = 0;

            for (int i = 0; i < chars.Length; i++)
            {
                c = chars[i];
                if ((c >= 0x0001) && (c <= 0x007F))
                {
                    utfLength++;
                }
                else if (c > 0x07FF)
                {
                    utfLength += 3;
                }
                else
                {
                    utfLength += 2;
                }
            }

            return utfLength;
        }

        private void encodeUTF8toBuffer(char[] chars, byte[] buffer )
        {
            int c = 0;
            int count = 0;

            for (int i = 0; i < chars.Length; i++)
            {
                c = chars[i];
                if ((c >= 0x0001) && (c <= 0x007F))
                {
                    buffer[count++] = (byte)c;
                }
                else if (c > 0x07FF)
                {
                    buffer[count++] = (byte)(0xE0 | ((c >> 12) & 0x0F));
                    buffer[count++] = (byte)(0x80 | ((c >> 6) & 0x3F));
                    buffer[count++] = (byte)(0x80 | ((c >> 0) & 0x3F));
                }
                else
                {
                    buffer[count++] = (byte)(0xC0 | ((c >> 6) & 0x1F));
                    buffer[count++] = (byte)(0x80 | ((c >> 0) & 0x3F));
                }
            }
        }
	}
}

